
#include <vector>
#include <apr_strings.h>
#include "../iface/debug.h"
#include "../aprtools/APRFile.h"
#include "HTTPServer.h"

/** Get an (limited) APRTable from the input pipe. The table is
 * defined as being in the format:
 * 
 * - (KEY)(seperator)(VALUE)(eol)(KEY)(seperator)(VALUE)(eol)(KEY) ...
 * 
 * It is up to the caller to delete the returned APRTable.
 * 
 * @param t_mp The APR memory pool from which to produce the APRTable
 * instance.
 * 
 * @param clength The maximum number of bytes that is to be consumed
 * from the stream.  Prevents malicious attempts to fill memory.
 * 
 * @param separator The separator to tokenise on, in order to separate
 * the key and value pair.
 * 
 * @param eol The end-of-line marker token to find with the tokeniser,
 * splitting the individual key-value pair from each other.
 * 
 * @param bb The bucket brigade stream to read from to construct the
 * table.
 * 
 * @param waitinglock On optional lock to wait on if there is no data
 * yet, waking up the current thread in this function when data is
 * available. Set this to NULL to disable it.
 * 
 * @return A new APRTable instance. While it is up to the caller to
 * delete this new instance when it is finished with, it is still
 * dependent on the given memory pool. The memory pool should be
 * deleted AFTER this returned APRTable. */
APRTable *HTTPServer::getTable(apr_pool_t *t_mp, apr_size_t clength, const char *separator, const char *eol,
                               APRBucketBrigade *bb, APRMutex *waitinglock)
{
   int isfirst = 1;
   APRTable *htable = new APRTable(MAX_HEADERS, t_mp);
   apr_size_t consumedbytes = 0;
   apr_size_t length_last_time = 0;
   char *linebuffer = (char *)malloc(sizeof(char)*MAX_BLOCK_SIZE);
   while (!(bb->isClosed()))
   {
      apr_size_t length_this_time = bb->realLength();
      if ((length_last_time != length_this_time) ||
          ((length_this_time + consumedbytes) >= clength) ||
          (((int64_t)length_last_time) == -1))
      {
         int realbytes = bb->readFromStream(linebuffer, MAX_BLOCK_SIZE - 1);
         linebuffer[realbytes] = '\0';
         char *k = strstr(linebuffer, eol);
         if ((!k) && ((length_this_time + consumedbytes) >= clength))
            k = linebuffer + strlen(linebuffer);
         if (k)
         {
            if (k == linebuffer)
            {
               bb->removeFromStream(strlen(eol));
               free(linebuffer);
               return htable;
            } else
            {
               int vallen = (k - linebuffer) + strlen(eol);
               char *newheader = (char *)apr_palloc(t_mp, sizeof(char)*(vallen+1));
               memcpy(newheader, linebuffer, k - linebuffer);
               newheader[k - linebuffer] = '\0';
               char *tremainder = NULL;
               char *bits = apr_strtok(newheader, separator, &tremainder);
               // isfirst is a fudge to remove extra characters in
               // later headers and make the first header line in
               // keeping (somewhat) with the rest
               if (tremainder[0] == ' ') tremainder++;
               htable->set(bits, tremainder);
               bb->removeFromStream(vallen);
               consumedbytes += vallen;
               isfirst = 0;
            }
         } else
         {
            if (length_this_time > (MAX_BLOCK_SIZE - 1))
            {
               errprint("data too big");
               free(linebuffer);
               delete htable;
               return NULL;
            }
         }
         length_last_time = length_this_time;
      } else if (waitinglock)
      {
         waitinglock->wait();
      }
   }
   free(linebuffer);
   return htable;
}

/** Get an APRTable from the input pipe. The table is defined as being
 * in the format:
 * 
 * - (KEY): (VALUE)CRLF_STR(KEY): (VALUE)CRLF_STR(KEY) ...
 * 
 * It is up to the caller to delete the returned APRTable.
 * 
 * @param i The thread this function belongs to, and which pipe to
 * take.
 * 
 * @param t_mp The APR memory pool from which to produce the APRTable
 * instance.
 * 
 * @return A new APRTable instance. While it is up to the caller to
 * delete this new instance when it is finished with, it is still
 * dependent on the given memory pool. The memory pool should be
 * deleted AFTER this returned APRTable. */
APRTable *HTTPServer::getHeaders(int i, apr_pool_t *t_mp)
{
   APRTable *retcls = getTable(t_mp, (apr_size_t)-1, ": ", CRLF_STR, pipearray[i].recv_to_handler, pipearray[i].waiting);
   if (!retcls) pipearray[i].handler_to_send->writeEOS();
   return retcls;
}

/** Get an (limited) APRTable from the input pipe. The table is
 * defined as being in the format:
 * 
 * - (KEY): (VALUE)CRLF_STR(KEY): (VALUE)CRLF_STR(KEY) ...
 * 
 * It is up to the caller to delete the returned APRTable.
 * 
 * @param i The thread this function belongs to, and which pipe to
 * take.
 * 
 * @param t_mp The APR memory pool from which to produce the APRTable
 * instance.
 * 
 * @param clength The maximum number of bytes that is to be consumed
 * from the stream.  Prevents malicious attempts to fill memory.
 * 
 * @return A new APRTable instance. While it is up to the caller to
 * delete this new instance when it is finished with, it is still
 * dependent on the given memory pool. The memory pool should be
 * deleted AFTER this returned APRTable. */
APRTable *HTTPServer::getPOSTData(int i, apr_pool_t *t_mp, apr_size_t clength)
{
   APRTable *retcls = getTable(t_mp, clength, "=", "&", pipearray[i].recv_to_handler, pipearray[i].waiting);
   if (!retcls) pipearray[i].handler_to_send->writeEOS();
   return retcls;
}

/** Get the content length from the table of headers.
 * 
 * @param htable The table of headers as an APRTable.
 * 
 * @return The content length. */
apr_off_t HTTPServer::getContentLength(const APRTable *htable)
{
   const char *clength_str = htable->get("Content-Length");
   if (clength_str)
   {
      apr_off_t clength_true = 0;
      sscanf(clength_str, "%" APR_OFF_T_FMT, &clength_true);
      return clength_true;
   }
   return (apr_size_t)-1;
}

/** Serve a file if it exists on disk and in the white list.
 * 
 * @param htable The table of incoming headers.
 * 
 * @param i The thread this function belongs to, and which pipe to
 * use.
 * 
 * @param t_mp The APR memory pool to use for any memory usage.
 * 
 * @param fileswhitelist The white list of files that are okay to
 * serve over HTTP.
 * 
 * @return True if the file was served, false if the file was not on
 * the whitelist/does not exist. */
int HTTPServer::serveFilesFromWhiteList(const APRTable *htable, int i, apr_pool_t *t_mp, const char **fileswhitelist)
{
   apr_status_t status;
   char *dstr = apr_pstrdup(t_mp, htable->get("GET"));
   char *httpver = NULL;
   char *firsttok = apr_strtok(dstr, " ", &httpver);
   int count = 0;
   int passes = 0;
   // see if it matches any in the white list
   while (fileswhitelist[count] != NULL)
      passes |= (strcmp(fileswhitelist[count++], firsttok) == 0);
   if (passes == 0)
   {
      /*
      if (strcmp(getClientAddressAsStaticString(i), "127.0.0.1") != 0)
         errprint("%s tried to connect but '%s' not on whitelist!", getClientAddressAsStaticString(i), firsttok);
      */
      return 0;
   }
   apr_file_t *newFP;
   char *filetoget = (char *)apr_palloc(t_mp, sizeof(char)*(strlen(firsttok)+2));
   filetoget[0] = '.';
   strcpy(&(filetoget[1]), firsttok);
   status = APRFile::openWrapper(&newFP, filetoget, APR_FOPEN_READ|APR_FOPEN_BINARY, APR_FPROT_OS_DEFAULT, t_mp);
   if (status != APR_SUCCESS) { CheckAPRError(status); return 0; }
   const char *firstheader = "HTTP/1.0 200 OK" CRLF_STR;
   pipearray[i].handler_to_send->writeToStream(firstheader, strlen(firstheader));
   apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
   status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
   CheckAPRError(status);
#else
   apr_off_t offset = 0;
   status = apr_file_seek(newFP, APR_END, &offset);
   CheckAPRError(status);
   finfo.size = offset;
   offset = 0;
   status = apr_file_seek(newFP, APR_SET, &offset);
   CheckAPRError(status);
#endif
   status = apr_file_close(newFP);
   CheckAPRError(status);
   pipearray[i].handler_to_send->writefToStream("Content-Length: %" APR_OFF_T_FMT, finfo.size);
   pipearray[i].handler_to_send->writeToStream(CRLF_STR CRLF_STR, strlen(CRLF_STR)*2);
   // silly numbers to say - just use all of it!
   pipearray[i].handler_to_send->writeFileToStream(filetoget, 0, finfo.size, 0);
   pipearray[i].handler_to_send->writeEOS();
   return 1;
}

/** Put a HTML body which is a large header as a reply.
 * 
 * @param i The thread this function belongs to, and which pipe to
 * use.
 *
 * @param t_mp The APR memory pool to use to allocate from.
 * 
 * @param body The body of the HTML document. */
void HTTPServer::replyWithSentence(int i, apr_pool_t *t_mp, const char *body)
{
   static const char *opening = "<html><h2>" CRLF_STR;
   static const char *closing = "</h2></html>" CRLF_STR;
   static const char *firstheader = "HTTP/1.0 200 OK" CRLF_STR;
   pipearray[i].handler_to_send->writeToStream(firstheader, strlen(firstheader));
   apr_size_t clength = strlen(opening)+strlen(closing)+strlen(body);
   pipearray[i].handler_to_send->writefToStream("Content-Length: %" APR_SIZE_T_FMT CRLF_STR CRLF_STR, clength);
   pipearray[i].handler_to_send->writeToStream(opening, strlen(opening));
   pipearray[i].handler_to_send->writeToStream(body, strlen(body));
   pipearray[i].handler_to_send->writeToStream(closing, strlen(closing));
   pipearray[i].handler_to_send->writeEOS();
}

/** Write a HTTP redirect as a reply.
 * 
 * @param i The thread this function belongs to, and which pipe to
 * use.
 *
 * @param t_mp The APR memory pool to use to allocate from.
 * 
 * @param relocation The URL to redirect to. */
void HTTPServer::redirectAfterPOST(int i, apr_pool_t *t_mp, const char *relocation)
{
   static const char *firstheader = "HTTP/1.0 302 Found" CRLF_STR;
   static const char *locheader = "Location: %s" CRLF_STR CRLF_STR;
   pipearray[i].handler_to_send->writeToStream(firstheader, strlen(firstheader));
   pipearray[i].handler_to_send->writefToStream(locheader, relocation);
   pipearray[i].handler_to_send->writeEOS();
}

/** Write a HTTP 404 not found error as a reply.
 * 
 * @param i The thread this function belongs to, and which pipe to
 * use. */
void HTTPServer::replyWithError404(int i)
{
   const char *firstheader = "HTTP/1.0 404 Not Found" CRLF_STR;
   pipearray[i].handler_to_send->writeToStream(firstheader, strlen(firstheader));
   pipearray[i].handler_to_send->writeEOS();
}

/** Handle and reply to a HTTP GET session request.
 * 
 * @param i The thread this function belongs to, and which pipe in the
 * #pipearray to use.
 * 
 * @param t_mp The APR memory pool to use to allocate from. */
void HTTPServer::handleGETSession(int i, apr_pool_t *t_mp)
{
   static const char *fileswhitelist[] = { "/files/blue.html", NULL };
   APRTable *headertable = getHeaders(i, t_mp);
   if (headertable == NULL) { replyWithError404(i); errprint("Header table missing (out of memory?)."); return; }
   if (headertable->isEmpty()) { replyWithError404(i); errprint("Header table empty (unknown error)."); return; }
   if (serveFilesFromWhiteList(headertable, i, t_mp, fileswhitelist)) { return; }
   replyWithError404(i);
}

/** Handle and reply to a HTTP POST session request.
 * 
 * @param i The thread this function belongs to, and which pipe in the
 * #pipearray to use.
 *
 * @param t_mp The APR memory pool to use to allocate from. */
void HTTPServer::handlePOSTSession(int i, apr_pool_t *t_mp)
{
   if (strcmp(getClientAddressAsStaticString(i), "127.0.0.1") != 0)
   {
      errprint("erroneous post by %s", getClientAddressAsStaticString(i));
      pipearray[i].handler_to_send->writeEOS();
      return;
   }
   APRTable *headertable = getHeaders(i, t_mp);
   apr_off_t contentlength = getContentLength(headertable);
   APRTable *posttable = getPOSTData(i, t_mp, contentlength);
   redirectAfterPOST(i, t_mp, "/hosted_files/blue.html");
   delete posttable;
   delete headertable;
}

/** Start handling clients to the server (the client handler threads
 * run this).
 * 
 * @param threadid The integer thread (and pipe in the #pipearray) ID
 * number.
 * 
 * @param localmp The memory pool local to this thread. */
void HTTPServer::handleClientsOnThread(int threadid, apr_pool_t *localmp)
{
   pipearray[threadid].sessionstart->lock();
   while (!sessionstop)
   {
      pipearray[threadid].sessionstart->wait();
      if (sessionstop) break;
      apr_pool_t *sp;
      apr_pool_create(&sp, localmp);
      pipearray[threadid].waiting->lock();
      while ((pipearray[threadid].recv_to_handler) &&
             (pipearray[threadid].recv_to_handler->realLength() < 4) &&
             (!(pipearray[threadid].recv_to_handler->isClosed())))
         pipearray[threadid].waiting->wait();
      char fst[5];
      if ((pipearray[threadid].recv_to_handler) &&
          (pipearray[threadid].recv_to_handler->realLength() >= 4))
      {
         pipearray[threadid].recv_to_handler->readFromStream(fst, 4);
         fst[4] = '\0';
      }
      else
         fst[0] = '\0';
      if ((!(pipearray[threadid].handler_to_send->isClosed())) && 
          (!(pipearray[threadid].recv_to_handler->isClosed())))
      {
         if (strcmp(fst, "GET ") == 0)
            handleGETSession(threadid, localmp);
         else if (strcmp(fst, "POST") == 0)
            handlePOSTSession(threadid, localmp);
         else
            pipearray[threadid].handler_to_send->writeEOS();
      } else
         pipearray[threadid].handler_to_send->writeEOS();
      // Make sure these are closed so that this pipe can be reused.
      pipearray[threadid].recv_to_handler->clear();
      pipearray[threadid].recv_to_handler->writeEOS();

      pipearray[threadid].waiting->unlock();
      apr_pool_clear(sp);
      apr_pool_destroy(sp);
   }
   pipearray[threadid].sessionstart->unlock();
}

/** Start handling clients to the server (the client handler threads
 * run this).
 * 
 * @param tthread The APR pointer to this thread grabbed.
 * 
 * @param args The void pointer given to the thread. This is actually
 * a pointer to an array of two void pointers, which are the this
 * pointer and a pointer to the thread ID respectively.
 * 
 * @return Return is NULL. */
void *HTTPServer::handleClientsOnThreadStatic(apr_thread_t *tthread, void *args)
{
   apr_status_t status;
   HTTPServer *kthis = *((HTTPServer **)(((void **)(args))[0]));
   int i = *((int *)(((void **)(args))[1]));
   apr_pool_t *upperpool, *t_mp;
   upperpool = apr_thread_pool_get(tthread);
   status = apr_pool_create(&t_mp, upperpool);
   CheckAPRError(status);
   
   kthis->handleClientsOnThread(i, t_mp);

   // do not clear memory pool t_mp, as bucket brigades may be
   // remaining
   ERR_remove_state(0);
   apr_thread_exit(tthread, APR_SUCCESS);
   return NULL;
}
